use glam::DVec3;
use crate::{Interval, Ray };

#[derive(Default, Clone, Copy)]
pub struct AABB {
    pub x: Interval,
    pub y: Interval,
    pub z: Interval,
}

impl AABB {
    pub fn new(x: Interval, y: Interval, z: Interval) -> Self {
        let mut value = Self { x, y, z };
        value.pad_to_minimums();
        value
    }

    pub fn from_two_aabb(a: &Self, b: &Self) -> Self {
        let mut value = Self {
            x: Interval::from_two_interval(&a.x, &b.x),
            y: Interval::from_two_interval(&a.y, &b.y),
            z: Interval::from_two_interval(&a.z, &b.z),
        };
        value.pad_to_minimums();
        value
    }
    
    pub fn from_two_point(a: DVec3, b: DVec3) -> Self {
        let x = Interval::new(a.x, b.x);
        let y = Interval::new(a.y, b.y);
        let z = Interval::new(a.z, b.z);
        let mut value = Self::new(x, y, z);
        value.pad_to_minimums();
        value
    }

    pub fn hit(&self, ray: &Ray, mut t: Interval) -> bool {
        let p = ray.orig;
        let d = ray.dir;

        for axis in 0..3 {
            let ax = self.axis_interval(axis);
            let ad_inv = 1.0 / d[axis];

            let t0 = (ax.min - p[axis]) * ad_inv;
            let t1 = (ax.max - p[axis]) * ad_inv;

            if t0 < t1 {
                t.min = t0.max(t.min);
                t.max = t1.min(t.max);
            } else {
                t.min = t1.max(t.min);
                t.max = t0.min(t.max);
            }

            if t.max <= t.min { return false }
        }

        true
    }

    pub fn axis_interval(&self, n: usize) -> &Interval {
        match n {
            0 => &self.x,
            1 => &self.y,
            2 => &self.z,
            _ => panic!("invalid AABB axis"),
        }
    }

    pub fn longest_axis(&self) -> usize {
        let x = self.x.size();
        let y = self.y.size();
        let z = self.z.size();

        if x > y && x > z { 0 }
        else if y > z { 1 }
        else { 2 }
    }

    fn pad_to_minimums(&mut self) {
        let delta = 0.0001;
        if self.x.size() < delta { self.x.expand(delta) }
        if self.y.size() < delta { self.y.expand(delta) }
        if self.z.size() < delta { self.z.expand(delta) }
    }
}

impl std::ops::Add<DVec3> for AABB {
    type Output = Self;

    fn add(self, rhs: DVec3) -> Self::Output {
        Self {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}